﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Lib = Krile.Kernel.Control.Surface;
using Krile.Kernel.Bridges;

namespace Krile.Forms.MainForm.Controls.Surface
{
    public class CompositionManager
    {
        object msgLocker = new object();
        IEnumerable<Lib.MessageObject> msgObjs;
        List<CompositeDescriptor> descs;
        public CompositionManager()
        {
            msgObjs = null;
            descs = new List<CompositeDescriptor>();
        }

        /// <summary>
        /// コンポジション対象のメッセージオブジェクト一覧を変更します。
        /// </summary>
        /// <param name="msgobjs">新しいコンポジション対象オブジェクト</param>
        public void ChangeCompositeTarget(IEnumerable<Lib.MessageObject> msgobjs)
        {
            lock (msgLocker)
            {
                msgObjs = msgobjs;
                descs.Clear();
                _compozHeight = 0;
            }
        }

        public void ReleaseComposite()
        {
            lock (msgLocker)
            {
                msgObjs = null;
                descs.Clear();
                _compozHeight = 0;
            }
        }

        public bool IsMsgobjsValid(IEnumerable<Lib.MessageObject> msgobjs)
        {
            return msgobjs == msgObjs;
        }

        private int _compozHeight = 0;
        public int CompositedHeight { get { return _compozHeight; } }

        public bool Compositable
        {
            get { return msgObjs != null; }
        }

        /// <summary>
        /// コンポジションを更新します。
        /// </summary>
        /// <param name="clientWidth">クライアント領域の幅</param>
        public void UpdateComposition(int clientWidth)
        {
            if (msgObjs == null)
                return;
            descs.Clear();
            _compozHeight = 0;
            if (msgObjs == null) return;
            Stack<CompositeDescriptor> descriptLineWaits = new Stack<CompositeDescriptor>();
            //メッセージオブジェクト一覧をロック
            lock (msgLocker)
            {
                int hsum = 0;
                int pLHeight = 0;
                int xp = 0;
                foreach (var m in msgObjs)
                {
                    //コンポジションを行うデータの作成
                    Compositor cpz = new Compositor(clientWidth, xp, pLHeight);
                    //コンポジション
                    m.Composite(cpz);
                    //コンポジションデスクリプタを生成
                    var h = cpz.Heights;
                    CompositeDescriptor desc = new CompositeDescriptor(clientWidth, h, xp);
                    //X位置を更新
                    xp = cpz.CurrentXPosition;

                    //行の高さが確定したら行確定待ちスタックに積んだデスクリプタに通知
                    if (h != null && h.Length > 0)
                    {
                        while (descriptLineWaits.Count > 0)
                            descriptLineWaits.Pop().ApplyHeight(h[0]);
                        //高さ合計の算出
                        foreach (var i in h)
                        {
                            hsum += i;
                        }
                    }
                    //最終行高さの更新
                    pLHeight = cpz.CurLineHeight;
                    //行確定待ちスタックにプッシュ、リストに追加
                    descriptLineWaits.Push(desc);
                    descs.Add(desc);
                }
                //行確定待ちスタックに積んだアイテムの処理
                while (descriptLineWaits.Count > 0)
                    descriptLineWaits.Pop().ApplyHeight(pLHeight);
                //高さ合計の最終処理
                hsum += pLHeight;
                _compozHeight = hsum;
            }
        }

        /// <summary>
        /// すでに作成したコンポジションを利用して描画します。
        /// </summary>
        /// <param name="g">描画を行うグラフィックコンテキスト</param>
        /// <param name="leftTop">描画の左上位置</param>
        public void Draw(Graphics g, Point leftTop, IForm parentForm)
        {
            if (descs.Count == 0 || msgObjs == null) return;
            lock (msgLocker)
            {
                int desci = 0;
                int heightSum = 0;
                foreach (var m in msgObjs)
                {
                    descs[desci].SetLeftTop(new Point(leftTop.X, leftTop.Y + heightSum));
                    m.Draw(g, descs[desci]);
                    if (descs[desci].Heights != null)
                    {
                        for (int i = 0; i < descs[desci].Heights.Length - 1; i++)
                        {
                            heightSum += descs[desci].Heights[i];
                        }
                    }
                    desci++;
                }
            }
        }

    }

    public class Compositor : Lib.Compositor
    {
        private int clientWidth;
        private int curLineHeight;
        public int CurLineHeight { get { return curLineHeight; } }
        public Compositor(int cWidth, int xpos, int cLineHeight)
        {
            clientWidth = cWidth;
            CurrentXPosition = xpos;
            curLineHeight = cLineHeight;
        }

        public override int ClientWidth
        {
            get { return clientWidth; }
        }

        public override int CurrentXPosition { get; set; }

        List<int> heightCollections = new List<int>();
        public int[] Heights { get { return heightCollections.Count > 0 ? heightCollections.ToArray() : null; } }

        public override void MoveNewLine()
        {
            heightCollections.Add(curLineHeight);
            curLineHeight = 0;
            CurrentXPosition = 0;
        }

        public override void SetCurrentLineHeight(int h)
        {
            if (h > curLineHeight)
                curLineHeight = h;
        }
    }

    public class CompositeDescriptor :Lib.CompositeDescriptor
    {
        public CompositeDescriptor(int cWidth, int[] heights, int x)
        {
            this.cWidth = cWidth;
            this.heights = heights;
            this.xp = x;
        }

        public void ApplyHeight(int height)
        {
            if (this.heights == null)
            {
                this.heights = new[] { height };
            }
            else
            {
                int[] na = new int[this.heights.Length + 1];
                this.heights.CopyTo(na, 0);
                na[this.heights.Length] = height;
                this.heights = na;
            }

        }

        private int cWidth;
        public override int ClientWidth
        {
            get { return cWidth; }
        }

        private int[] heights;
        public override int[] Heights
        {
            get { return heights; }
        }

        private int xp;
        public override int StartXPos
        {
            get { return xp; }
        }

        /// <summary>
        /// 左上座標をセット
        /// </summary>
        public void SetLeftTop(Point ltp)
        {
            this.lt = ltp;
        }

        private Point lt = Point.Empty;
        public override Point LeftTop
        {
            get { return lt; }
        }

    }
}
